package ss.pku.jsa.type.inference;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;

import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;

import ss.pku.jsa.type.constraint.solver.QError;
import ss.pku.jsa.type.io.MethodQSig;


@SuppressWarnings({ "rawtypes", "unchecked" })
public class MethodQType extends QType {
	// private static final int kind = 1;
	private List<RefQType> parameters;
	private QType returnQType;
	private ObjectQType thisQType;
	private boolean constructor;
	public String shortName;
	public boolean isStatic;
	public boolean isNative;

	public MethodQType(IMethodBinding m, Location l, boolean original) {
		m.getDeclaringClass().getName();

		this.name = m.getKey();
		this.shortName = m.getName();
		this.original = original;
		this.isStatic = ((m.getModifiers() & 0x8) != 0);
		this.isNative = ((m.getModifiers() & 0x100) != 0);

		ITypeBinding r = m.getReturnType();
		if (m.isConstructor()) {
			this.thisQType = ((ObjectQType) QType.getFreshQType(
					m.getDeclaringClass(), 0, l, this.name + "_cons_ret",
					original, true));
			this.returnQType = this.thisQType;
			((ObjectQType) this.returnQType).makeInstantiationEdges();

			if (JqualPlugin.inferInterned) {
				ClassQType cqt = JqualPlugin.analysis.getClassQType(TypeKey
						.getTypeKey(m.getDeclaringClass()));
				CQual.makeEdge(Constraint.Kind.LT, this.returnQType.q, cqt.q,
						JqualPlugin.analysis.gateInternedOnly, l, 0,
						QError.INTERNED_INSTANCE);
			}
		} else {
			this.thisQType = ((ObjectQType) QType.getFreshQType(
					m.getDeclaringClass(), 0, l, this.name + "_this", original,
					true));

			if (r != null) {
				this.returnQType = QType.getFreshQType(r, r.getDimensions(), l,
						this.name + "_ret", original, true);
			}

		}

		int numParams = m.getParameterTypes().length;
		this.parameters = new ArrayList(numParams);

		boolean isPublic = (m.getModifiers() & 0x1) != 0;

		for (int i = 0; i < numParams; i++) {
			if (l == null)
				throw new NullPointerException();

			ITypeBinding param = m.getParameterTypes()[i];
			RefQType pqt = QType.getFreshRefQType(param, param.getDimensions(),
					l, this.name + "_param" + (i + 1), original, false);
			if ((isPublic) && (JqualPlugin.inferOpaque)
					&& (JqualPlugin.ffiInferPublic)) {
				CQual.makeEdge(Constraint.Kind.LT,
						JqualPlugin.analysis.qual_public,
						pqt.contents.getQual(), QualGate.EMPTY, l, 0,
						QError.FFI_PUBLIC_METHOD);
			}
			this.parameters.add(i, pqt);
			pqt.setPreferred(false);
		}
		this.constructor = m.isConstructor();
	}

	public QType copy(Location l, String name) {
		throw new UnsupportedOperationException(
				"when do you need to clone a class QType?");
	}

	public void setLocation(Location l) {
		if (l == null)
			throw new NullPointerException();

		this.l = l;
	}

	public QType getReturnQType() {
		return this.returnQType;
	}

	public void makeConservativeAssumptions() {
		if (!isConstructorQType()) {
			if (this.returnQType != null)
				this.returnQType.getQual().makeConservativeAssumptions(false);
			this.thisQType.getQual().makeConservativeAssumptions(true);
		}

		for (Iterator<RefQType> p = this.parameters.iterator(); p.hasNext();) {
			QType pqt = ((QType) p.next()).deref();
			pqt.getQual().makeConservativeAssumptions(true);
		}
	}

	public void setOriginal(boolean original) {
		this.original = original;

		if (this.returnQType != null) {
			this.returnQType.setOriginal(original);
		}
		if (this.thisQType != null) {
			this.thisQType.setOriginal(original);
		}
		for (Iterator<RefQType> p = this.parameters.iterator(); p.hasNext();) {
			QType pqt = (QType) p.next();
			if (pqt != null)
				pqt.setOriginal(original);
		}
	}

	public String toString() {
		StringBuffer s = new StringBuffer();
		s.append("(" + this.returnQType + ") " + getName() + "\n");
		s.append("\tthis QType: " + this.thisQType + "\n");
		if (this.parameters.size() > 0) {
			s.append("Parameter QTypes for " + getName() + ":\n");
			for (Iterator<RefQType> p = this.parameters.iterator(); p.hasNext();) {
				s.append("\t" + p.next());
				if (p.hasNext())
					s.append("\n");
			}
		}
		return s.toString();
	}

	public boolean isPrimitive() {
		return false;
	}

	public int getKind() {
		return 1;
	}

	public List<RefQType> getParameterQTypes() {
		return this.parameters;
	}

	public ObjectQType getThisQType() {
		return this.thisQType;
	}

	public boolean isConstructorQType() {
		return this.constructor;
	}

	public void merge(QType q, Location loc, QError error) {
		throw new UnsupportedOperationException(
				"Do we need to merge MethodQTypes?");
	}

	public void setGlobal(boolean global) {
		throw new UnsupportedOperationException(
				"Why would we need to set a methodQType as global?");
	}

	public void setPreferred(boolean preferred) {
		throw new UnsupportedOperationException(
				"Why would we need to set a methodQType as preferred?");
	}

	public QType getEcr() {
		throw new UnsupportedOperationException(
				"Why would we need to take ecr of a methodQType?");
	}

	public int getSize() {
		int ret = 0;
		ret += this.thisQType.getSize() + this.returnQType.getSize();
		for (QType p : this.parameters)
			ret += p.getSize();
		return ret;
	}

	public String toStringWithQuals(int fieldIndent) {
		StringBuilder ret = new StringBuilder();

		ret.append("\n");
		if (this.returnQType != null)
			ret.append("   ret:" + this.returnQType.toStringWithQuals(1) + "\n");
		ret.append("   this:" + this.thisQType.toStringWithQuals(1));
		int pos = 1;
		for (RefQType p : this.parameters) {
			ret.append("\n   p" + pos++ + ":" + p.toStringWithQuals(1));
		}

		return ret.toString();
	}

	public int getReadonlyPositions() {
		if (!this.original)
			return 0;
		int result = (isConstructorQType()) || (this.isStatic) ? 0 : 1;
		if ((this.returnQType != null)
				&& ((this.returnQType instanceof ObjectQType)))
			result++;
		for (RefQType param : this.parameters) {
			if ((param.deref() instanceof ObjectQType))
				result++;
		}
		return result;
	}

	public int getFFIPositions() {
		if (!this.isNative)
			throw new IllegalArgumentException("NOT Native: " + this);
		int result = this.parameters.size();
		if (this.returnQType != null)
			result++;
		return result;
	}

	public int getReadonlyInferred() {
		if (!this.original) {
			return 0;
		}
		int result = 0;

		if ((this.returnQType != null)
				&& ((this.returnQType instanceof ObjectQType))
				&& (this.returnQType.getQual().isReadonly()))
			result++;
		if ((!isConstructorQType()) && (!this.isStatic)
				&& (this.thisQType.getQual().isReadonly()))
			result++;

		for (RefQType param : this.parameters) {
			if ((!(param.deref() instanceof ObjectQType))
					|| (!param.deref().getQual().isReadonly()))
				continue;
			result++;
		}

		return result;
	}

	public MethodQSig methodQSig() {

		List qParams = new Vector();

		for (RefQType pqt : this.parameters) {
			qParams.add(pqt.getQual().getInferred());
		}

		Qualifier qt = getThisQType().getQual();
		String qThis = qt.getInferred();

		String qReturn = "";
		if (getReturnQType() != null) {
			Qualifier qr = getReturnQType().getQual();
			qReturn = qr.getInferred();
		}

		return new MethodQSig(qParams, qReturn, qThis, this.name);
	}
}
